增量引擎调用流程总结.md•18.6 kB
# 增量引擎调用流程总结:从前端到后端
> 本文档总结增量引擎从前端开始一步一步往后调用的完整流程,涵盖创建阶段、首次计算阶段、状态变更阶段和更新阶段。
**基于**: Cangjie/KoalaRuntime 实现
**核心文件**: `runtime/src/core/StateManagerImpl.cj`, `runtime/src/core/ScopeImpl.cj`, `runtime/src/core/memo.cj`
---
## 目录
1. [整体调用流程概览](#1-整体调用流程概览)
2. [阶段一:前端入口 - memoRoot 创建](#2-阶段一前端入口---memoroot-创建)
3. [阶段二:首次计算 - getValue 触发](#3-阶段二首次计算---getvalue-触发)
4. [阶段三:子作用域创建 - memo/NodeAttach](#4-阶段三子作用域创建---memonodeattach)
5. [阶段四:状态变更与失效传播](#5-阶段四状态变更与失效传播)
6. [阶段五:增量更新执行](#6-阶段五增量更新执行)
7. [关键机制说明](#7-关键机制说明)
---
## 1. 整体调用流程概览
```
前端入口
↓
memoRoot(rootNode, update)
↓
updatableNode → 创建根 Scope(冻结状态)
↓
首次访问 rootScope.getValue()
↓
执行 update(冻结窗口内)
├─ getMemoScope → 创建子作用域
├─ param() → 包装参数为 State
├─ isUnchanged() → 判断是否需要重算
└─ recache() → 计算并缓存
↓
状态变更(State.setValue)
↓
updateSnapshot() → 快照推进
↓
失效传播(invalidate)
↓
增量更新(仅重算受影响部分)
```
---
## 2. 阶段一:前端入口 - memoRoot 创建
### 2.1 调用链
```
用户代码: memoRoot(rootNode, update)
↓
memoRoot(node, update)
↓
GlobalStateManager.instance()
↓
StateManagerImpl.updatableNode(node, lambda1, None)
↓
ScopeImpl.init(None, 2, lambda2, lambda3, None)
↓
返回 ComputableState<Node>(根作用域,尚未计算)
```
### 2.2 详细步骤
#### 步骤 1: memoRoot 被调用
```cj
public func memoRoot<Node>(node: Node, update: (StateManagerImpl) -> Unit): ComputableState<Node>
where Node <: IncrementalNode {
let manager = GlobalStateManager.instance()
manager.updatableNode(node, { => manager.runWithFrozen(update)}, None)
}
```
**执行内容**:
1. 获取全局状态管理器:`GlobalStateManager.instance()`
2. **创建 lambda1**: `{ => manager.runWithFrozen(update) }`
- 此时 lambda 只是被创建,**尚未执行**
- `update` 参数被捕获在闭包中
3. 调用 `updatableNode(node, lambda1, None)`
#### 步骤 2: updatableNode 创建根作用域
```cj
public func updatableNode<Node>(node: Node, update: () -> Unit, cleanup: ?() -> Unit): ComputableState<Node>
where Node <: IncrementalNode {
let scope = ScopeImpl<Node>(
None,
2,
{ => update(); node }, // lambda2
{_ => cleanup?()}, // lambda3
None
)
scope.manager = this
scope.nodeAttached = node
scope.nodeRef = node
scope.param(0, {=> dumpHierarchyOf(scope)}, ...) // lambda4
scope.param(1, {=> dumpHierarchyOf(node)}, ...) // lambda5
return scope
}
```
**执行内容**:
1. **创建 lambda2**: `{ => update(); node }`(保存为 `myCompute`,不执行)
2. **创建 lambda3**: `{_ => cleanup?()}`(保存为 `myCleanup`,不执行)
3. **创建根作用域**: `ScopeImpl.init(None, 2, lambda2, lambda3, None)`
- `recomputeNeeded = true`(默认值,首次需要计算)
4. **设置作用域属性**:
- `scope.manager = this`
- `scope.nodeAttached = node`
- `scope.nodeRef = node`
5. **创建参数状态**:
- `scope.param(0, lambda4, ...)`(lambda4 保存,不执行)
- `scope.param(1, lambda5, ...)`(lambda5 保存,不执行)
6. **返回根作用域**
**关键点**:
- 所有 lambda 在此阶段只是被创建和保存,**均未执行**
- 根作用域处于"待计算"状态
---
## 3. 阶段二:首次计算 - getValue 触发
### 3.1 调用链
```
用户代码: rootScope.getValue() 或访问 .value
↓
ScopeImpl.getValue()
↓
ScopeImpl.isUnchanged()
├─ isRecomputeNeeded() → true
├─ 保存当前作用域: scopeInternal = manager.currentScope
├─ 切换作用域: manager.currentScope = this
└─ 返回 false(需要重算)
↓
ScopeImpl.getValue() 继续
├─ 获取 lambda2: myCompute.getOrThrow()
└─ 执行 lambda2: compute()
↓
执行 lambda1: manager.runWithFrozen(update)
├─ frozen = true(进入冻结窗口)
├─ 执行用户 update(manager)
└─ frozen = old(退出冻结窗口)
↓
返回 node
↓
ScopeImpl.recache(node)
├─ 恢复作用域: manager.currentScope = scopeInternal
├─ 更新缓存: myValue = node, recomputeNeeded = false
├─ detachChildScopes(None)
├─ parent?.increment(...)
├─ nodeAttached?.incrementalUpdateDone(...)
└─ getCached()
└─ 返回 node
```
### 3.2 详细步骤
#### 步骤 1: getValue 被调用
```cj
public func getValue(): Value {
if (this.isUnchanged()) {
this.getCached()
} else {
let compute = this.myCompute.getOrThrow()
this.recache(compute())
}
}
```
#### 步骤 2: isUnchanged 判断(首次返回 false)
```cj
public func isUnchanged(): Bool {
if (this.isRecomputeNeeded()) {
this.incremental = None
this.nodeCount = 0
if (!this.isDisposed()) {
if(let Some(manager) <- this.manager) {
this.scopeInternal = manager.currentScope // 保存
manager.currentScope = this // 切换
}
}
return false // 需要重算
} else {
// 缓存命中,跳过计算
this.parent?.increment(...)
return true
}
}
```
**关键点**:
- 切换 `manager.currentScope` 后,后续的状态访问会在此作用域下登记依赖
#### 步骤 3: 执行计算 lambda2
```cj
let compute = this.myCompute.getOrThrow() // 获取 lambda2
this.recache(compute()) // 执行 lambda2
```
lambda2 的内容:
```cj
{ =>
update() // 这是 lambda1: { => manager.runWithFrozen(update) }
node
}
```
#### 步骤 4: 执行 lambda1(runWithFrozen)
```cj
func runWithFrozen(runnable: (StateManagerImpl) -> Unit): Unit {
let old = frozen
frozen = true // 进入冻结窗口(防止状态在重组期间被修改)
runnable(this) // 执行用户传入的 update(manager)
frozen = old // 退出冻结窗口
}
```
**执行内容**:
1. 设置 `frozen = true`(冻结状态修改)
2. **执行用户 update 函数**(在冻结窗口内)
- 用户代码可能调用 `memo`、`NodeAttach` 等
- 这些会创建子作用域并可能触发计算
3. 恢复 `frozen = old`
#### 步骤 5: recache 更新缓存
```cj
public func recache(newValue: Value): Value {
// 恢复作用域
if (!this.isDisposed()) {
if(let Some(manager) <- this.manager) {
manager.currentScope = this.scopeInternal
}
}
// 更新缓存值
let oldValue = this.myValue
this.myValue = newValue
this.myModified = this.myComputed && !equalValues(newValue, oldValue.getOrThrow())
this.myComputed = true
this.recomputeNeeded = false
// 维护子树
this.detachChildScopes(None)
this.parent?.increment(...)
this.nodeAttached?.incrementalUpdateDone(this.parent?.nodeRef ?? None)
return this.getCached()
}
```
---
## 4. 阶段三:子作用域创建 - memo/NodeAttach
### 4.1 在用户 update 中创建子作用域
当用户的 `update` 函数执行时,可能调用 `memo` 或 `NodeAttach`,这些会创建子作用域。
#### 示例:memo2 调用流程
```cj
func memo2<P1, P2, Value>(p1: P1, p2: P2, compute: (__memo_key: Hashscopeid, manager: StateManagerImpl, s1: State<P1>, s2: State<P2>) -> Value): Value {
let scope = manager.getMemoScope<Value>(_new_id, 2, None, None, None, false, None)
let s1 = scope.param(0, p1)
let s2 = scope.param(1, p2)
if (scope.isUnchanged()) {
scope.getCached()
} else {
scope.recache(compute(s1, s2))
}
}
```
### 4.2 子作用域创建步骤
#### 步骤 1: getMemoScope 获取/创建子作用域
```cj
public func getMemoScope<Value>(id: Hashscopeid, paramCount: Int64): MemoScope<Value> {
getMemoScope(id, paramCount, None, None, None, false, None)
}
```
**执行内容**:
1. 在父作用域中按 `id` 扫描兄弟链,查找可复用的子作用域
2. 如果未找到,创建新的 `ScopeImpl`
3. 将新作用域接入链表/树结构
#### 步骤 2: param 包装参数为 State
```cj
public func param<Value>(index: Int64, value: Value, name: ?String, contextLocal: Bool): State<Value> {
let params = this.params.getOrThrow()
let manager = this.manager.getOrThrow()
if (let Some(param) <- params[index]) {
// 复用已有参数状态
let state = (param as ParameterImpl<Value>).getOrThrow()
state.setValue(value)
return state
} else {
// 创建新的参数状态
let state = ParameterImpl<Value>(value, name, manager)
params[index] = state
return state
}
}
```
**关键点**:
- 参数被包装为 `ParameterImpl`(一种 State)
- 访问参数时会自动登记依赖
#### 步骤 3: isUnchanged 判断
```cj
if (scope.isUnchanged()) {
scope.getCached() // 缓存命中,跳过计算
} else {
scope.recache(compute(s1, s2)) // 需要重算
}
```
**首次调用**: `isUnchanged()` 返回 `false`,进入计算分支
#### 步骤 4: 执行 compute 并 recache
```cj
scope.recache(compute(s1, s2))
```
**执行内容**:
1. 执行用户传入的 `compute` 函数
2. 在 `compute` 中访问 `s1`、`s2` 时,会触发依赖登记
3. `recache` 缓存计算结果并维护子树
### 4.3 依赖登记机制
#### State 访问时登记依赖
```cj
// StateImpl.getValue()
private func onAccess(): Unit {
if(let Some(dep) <- this.manager?.getDependency()) {
this.dependencies?.register(dep) // 登记当前作用域为依赖
}
}
```
#### 参数访问时登记依赖
```cj
// ParameterImpl.getValue()
public func getValue(): Value {
if(let Some(dep) <- this.manager?.getDependency()) {
this.dependencies?.register(dep)
}
return this.value
}
```
**依赖关系建立**:
- State/Parameter → 记录依赖它的 Scope
- Scope → 通过 `manager.currentScope` 获取当前作用域
- 形成依赖链:State → Scope(依赖关系)
---
## 5. 阶段四:状态变更与失效传播
### 5.1 状态变更入口
用户代码设置状态值:
```cj
counter.setValue(10)
```
### 5.2 状态变更流程
#### 步骤 1: StateImpl.setValue
```cj
public func setValue(value: Value) {
this.current = value
this.updated = false
if (let Some(manager) <- this.manager) {
manager.updateNeeded = true // 标记需要更新
} else {
this.updateStateSnapshot()
}
}
```
**核心动作**: 不立即对比,只标记本轮需要更新
### 5.3 快照推进阶段
由外部驱动(下一帧/周期):
```cj
public func updateStateManager(manager!: StateManagerImpl = GlobalStateManager.instance()): Unit {
manager.updateSnapshot()
}
```
#### updateSnapshot 执行流程
```cj
public func updateSnapshot(): UInt64 {
this.checkForStateComputing()
var modified: UInt64 = 0
if (this.updateNeeded) {
// 阶段 1: 更新所有 State 的快照
if (!this.createdStates.isEmpty()) {
for (state in this.createdStates) {
state.updateStateSnapshot()
if (state.isModified()) { modified++ }
}
}
// 阶段 2: 消费脏作用域
while (!this.dirtyScopes.isEmpty()) {
let scopes = this.dirtyScopes.toArray()
this.dirtyScopes.clear()
for (scope in scopes) {
if (scope.isModified()) { modified++ }
}
}
this.updateNeeded = modified > 0
}
return modified
}
```
### 5.4 State 快照更新与失效触发
```cj
public func updateStateSnapshot(): Unit {
if (this.updated) {
this.modified = false
} else {
this.updated = true
this.modified = !equalValues(this.snapshot, this.current)
if (this.modified) {
this.snapshot = this.current
}
}
// 触发依赖失效
this.dependencies?.onUpdate(this.modified)
}
```
### 5.5 失效传播机制
#### 依赖集合触发失效
```cj
// Dependencies.onUpdate()
public func onUpdate(invalidate: Bool): Unit {
if (invalidate) {
if (let Some(dependencies) <- this.dependencies) {
for (dependency in dependencies) {
dependency.invalidate() // 调用每个依赖的 invalidate()
}
}
}
}
```
#### Scope 的失效逻辑
```cj
public func invalidate(): Unit {
if(let Some(manager) <- this.manager) {
let current = manager.currentScope
var scope: ManagedScope = this
while (true) {
if (Some(scope) == current) { break } // 避免重组中的广泛失效
scope.recomputeNeeded = true // 标记需要重算
if (let Some(parent) <- scope.parent) {
scope = parent // 向上传播
} else {
// 到达顶层,加入脏作用域集合
if (let Some(deps) <- scope.dependencies) {
if (!deps.empty) {
manager.addDirtyScope(scope)
}
}
break
}
}
}
}
```
**传播规则**:
- 向上传播到顶层作用域
- 若遇到"当前正在计算的作用域"(`currentScope`),则停止
- 到达顶层且存在依赖时,加入 `dirtyScopes`
---
## 6. 阶段五:增量更新执行
### 6.1 更新触发
当作用域被访问时(如 `getValue()`),会检查是否需要重算:
```cj
public func getValue(): Value {
if (this.isUnchanged()) {
this.getCached() // 缓存命中,跳过计算
} else {
let compute = this.myCompute.getOrThrow()
this.recache(compute()) // 需要重算
}
}
```
### 6.2 增量更新流程
#### 场景:只有组件 A 依赖的状态变更
```
状态变更: counter.setValue(10)
↓
updateSnapshot()
├─ counter.updateStateSnapshot() → modified=true
└─ counter.dependencies.onUpdate(true)
└─ A.invalidate()
├─ A.recomputeNeeded = true
└─ Root.recomputeNeeded = true
↓
下一次渲染: Root.getValue()
├─ Root.isUnchanged() → false(进入计算)
│ ├─ A.isUnchanged() → false(重算 A,读取 counter=10,recache)
│ └─ B.isUnchanged() → true(跳过,使用缓存)
└─ Root.recache() → 完成
```
#### 增量跳过机制
对于未变更的子树,使用 `IncrementalNode.incrementalUpdateSkip(count)` 快速跳过:
```cj
func incrementalUpdateSkip(count: UInt32) {
// 快速跳过未变更的子树节点
}
```
**结果**: 仅 A 重算、B 跳过;Root 快速完成
---
## 7. 关键机制说明
### 7.1 冻结窗口(Frozen Window)
**目的**: 防止状态在重组期间被修改
**实现**:
```cj
func runWithFrozen(runnable: (StateManagerImpl) -> Unit): Unit {
let old = frozen
frozen = true // 进入冻结窗口
runnable(this)
frozen = old // 退出冻结窗口
}
```
**效果**: 在 `update` 执行期间,状态修改被禁止
### 7.2 作用域切换(Scope Switching)
**目的**: 确保状态访问时能正确登记依赖
**实现**:
```cj
// 进入计算前
this.scopeInternal = manager.currentScope // 保存
manager.currentScope = this // 切换
// 计算完成后
manager.currentScope = this.scopeInternal // 恢复
```
**效果**: 在作用域计算期间,所有状态访问都会登记到当前作用域
### 7.3 依赖登记(Dependency Registration)
**时机**: 读时注册(Read-Time Registration)
**流程**:
1. 作用域计算中访问 State/Parameter
2. State/Parameter 的 `getValue()` 被调用
3. `onAccess()` 获取当前作用域(`manager.getDependency()`)
4. 将当前作用域注册到 State/Parameter 的 `dependencies`
**数据结构**:
- `State.dependencies: ?Dependencies` → 存储依赖该 State 的所有 Scope
- `Scope` 通过 `manager.currentScope` 获取当前作用域
### 7.4 失效传播(Invalidation Propagation)
**规则**:
1. 状态变更 → 标记 `updateNeeded = true`
2. 快照推进 → 比较 `snapshot` vs `current`,判定 `modified`
3. 失效触发 → `dependencies.onUpdate(true)` 调用每个依赖的 `invalidate()`
4. 向上传播 → Scope 的 `invalidate()` 向上传播到顶层
5. 加入脏集合 → 顶层作用域加入 `dirtyScopes`
### 7.5 增量更新(Incremental Update)
**策略**:
- **缓存命中**: `isUnchanged() == true` → 直接返回缓存,跳过计算
- **需要重算**: `isUnchanged() == false` → 执行 `compute()` 并 `recache()`
- **节点跳过**: 未变更子树使用 `incrementalUpdateSkip()` 快速跳过
**效果**: 只重算受影响的部分,最小化更新成本
---
## 总结
### 完整调用流程
1. **前端入口**: `memoRoot` → 创建根作用域(冻结状态)
2. **首次计算**: `getValue()` → 执行 `update`(冻结窗口内)
3. **子作用域**: `getMemoScope` → `param` → `isUnchanged` → `recache`
4. **依赖登记**: 状态访问时自动登记依赖关系
5. **状态变更**: `setValue` → 标记 `updateNeeded`
6. **快照推进**: `updateSnapshot` → 比较并触发失效
7. **失效传播**: `invalidate` → 向上传播到顶层
8. **增量更新**: 仅重算受影响的作用域,其他部分跳过
### 关键设计原则
1. **延迟执行**: Lambda 在创建时不执行,只在需要时才执行
2. **读时注册**: 依赖在状态访问时自动登记
3. **写时标记**: 状态变更只标记,不立即计算
4. **快照推进**: 在下一帧统一推进快照并触发失效
5. **增量重算**: 只重算受影响的部分,最大化缓存命中率
### 性能优化点
1. **作用域复用**: 通过 `id` 和参数匹配复用作用域
2. **缓存机制**: 未变更的作用域直接返回缓存
3. **增量跳过**: 未变更子树快速跳过,不参与计算
4. **冻结保护**: 防止重组期间的状态修改,保证一致性
---
**文档版本**: v1.0
**最后更新**: 2024